﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml.Linq;

using WeatherReport.DataModel;
using WeatherReport.Interfaces;

namespace WeatherReport.Services
{
    public class GlobalWeatherServiceAdapter : IWeatherService
    {
        private readonly GlobalWeatherSoapClient _client;

        private Dictionary<string, IList<string>> _citiesCache = new Dictionary<string, IList<string>>();

        private object _citiesCacheLock = new object();

        public GlobalWeatherServiceAdapter()
        {
            _client = new GlobalWeatherSoapClient("GlobalWeatherSoap");
        }

        public IList<string> GetCitiesByCountry(string countryName)
        {
            lock (_citiesCacheLock)
            {
                if (!_citiesCache.ContainsKey(countryName))
                {
                    string citiesData;

                    try
                    {
                        citiesData = _client.GetCitiesByCountry(countryName);
                    }
                    catch (Exception ex)
                    {
                        throw new WeatherServiceException(WeatherServiceExceptionReason.CityListUnavailable,
                            "Unable to get the city list for the country " + countryName, ex);
                    }

                    XDocument doc;
                    try
                    {
                        doc = XDocument.Parse(citiesData);
                    }
                    catch (Exception ex)
                    {
                        throw new WeatherServiceException(WeatherServiceExceptionReason.CityListInvalidData,
                            "Unable to process the city list data for the country " + countryName, ex);
                    }

                    if (doc.Root != null && doc.Root.Name == "NewDataSet")
                    {
                        IList<string> result = doc.Root.Elements().Descendants("City").Select(element => element.Value).ToList();
                        _citiesCache.Add(countryName, result);
                        return result;
                    }

                    return new string[0];
                }
            }

            return _citiesCache[countryName];
        }

        public WeatherInfo GetWeather(string city, string country)
        {
            string serviceRawData;

            try
            {
                serviceRawData = _client.GetWeather(city, country);
            }
            catch (Exception ex)
            {
                string msg = String.Format("Unable to get the weather data for {0} in {1}", city, country);
                throw new WeatherServiceException(WeatherServiceExceptionReason.WeatherInfoUnavailable, msg, ex);
            }

            if (serviceRawData.Contains("<?xml"))
            {
                try
                {
                    XDocument doc = XDocument.Parse(serviceRawData);
                    double? t = GetTemperature(doc.Root.Element("Temperature"));
                    double? wd = GetWindDirection(doc.Root.Element("Wind"));
                    double? ws = GetWindSpeed(doc.Root.Element("Wind"));
                    return new WeatherInfo(t, wd, ws);
                }
                catch (Exception ex)
                {
                    string msg = String.Format("Unable to process the weather data for {0} in {1}", city, country);
                    throw new WeatherServiceException(WeatherServiceExceptionReason.WeatherInfoInvalidData, msg, ex);
                }
            }

            throw new WeatherServiceException(WeatherServiceExceptionReason.WeatherInfoInvalidData,
                String.Format("Received non-XML response as the weather data for {0} in {1}", city, country));
        }

        private double? GetTemperature(XElement rawData)
        {
            string val = rawData.Value;
            Regex ex = new Regex(@"([0-9]+ C)");
            Match m = ex.Match(val);
            if (m.Success)
            {
                string degrees = val.Substring(m.Index, m.Length - 2);
                int res = 0;
                if (Int32.TryParse(degrees, out res))
                    return res;
            }

            return null;
        }

        private double? GetWindDirection(XElement rawData)
        {
            string val = rawData.Value;
            Regex ex = new Regex(@"([0-9]+ degrees)");
            Match m = ex.Match(val);
            if (m.Success)
            {
                string degrees = val.Substring(m.Index, m.Length - 8);
                int res = 0;
                if (Int32.TryParse(degrees, out res))
                {
                    return Math.PI * ((double)res) / 180.0;
                }
            }

            return null;
        }

        private double GetWindSpeed(XElement rawData)
        {
            const double MPH_TO_MS = 0.4470;
            string val = rawData.Value;
            Regex ex = new Regex(@"[0-9]+ MPH");
            Match m = ex.Match(val);
            if (m.Success)
            {
                string mph = val.Substring(m.Index, m.Length - 4);
                int res = 0;
                if (Int32.TryParse(mph, out res))
                {
                    return MPH_TO_MS * res;
                }
            }

            return 0.0;
        }
    }
}
